package com.zzj.security.jwt.config.security;

import com.alibaba.fastjson.JSON;
import com.zzj.security.jwt.config.security.service.CustomizeUserDetailsService;
import com.zzj.security.jwt.entity.dto.HttpResult;
import com.zzj.security.jwt.utils.JwtTokenUtil;
import com.zzj.security.jwt.utils.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName SpringSecurityConfig
 * @Description TODO
 * @Author zhijun zeng at 17:33 2020/5/17
 * @Version 1.0
 **/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启security注解
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private CustomizeUserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

    @Override
    public void configure(WebSecurity web) throws Exception {
        List<String> ignoringList = new ArrayList<>();
        ignoringList.add("/favicon.ico");
        ignoringList.add("/css/**");
        ignoringList.add("/fonts/**");
        ignoringList.add("/js/**");
        ignoringList.add("/lib/**");
        ignoringList.add("/libs/**");
        ignoringList.add("/modules/**");
        ignoringList.add("/plugins/**");
        web.ignoring().mvcMatchers(ignoringList.toArray(new String[]{}));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        /* 开启跨域共享    跨域伪造请求限制=无效 */
        http.cors().and().csrf().disable();

        http.headers().frameOptions().disable();
        http.headers().contentTypeOptions().disable();

        //http.addFilterBefore(veriryCodeFilter, UsernamePasswordAuthenticationFilter.class);

        /* 创建 UsernamePasswordAuthenticationFilter username 和 password 的登录页面 */
        // 不使用http.formLogin()默认登录的情况下,使用http.exceptionHandling().authenticationEntryPoint(loginUrlAuthenticationEntryPoint())  //使用新的登录EntryPoint
        // LoginUrlAuthenticationEntryPoint，继续使用 http.formLogin() 也可以
        http
            .authorizeRequests()
                .antMatchers("/login","/captcha/getImage","/login.html").permitAll()
                .antMatchers("/user/**").hasAuthority("user")
                //.antMatchers("/ajax/admin/sysUser")
                //.antMatchers("/ajax/admin/sysRole/**").hasAnyAuthority("sysadmin","admin")//.hasAnyAuthority("sysadmin","admin")  //.hasAnyRole("sysadmin","admin")
                //.antMatchers("/ajax/admin/sysMenu/**").authenticated()
                .anyRequest().authenticated()
            .and()
                .formLogin()
                .usernameParameter("username")
                .passwordParameter("password")
                .loginProcessingUrl("/login")// 默认处理登录的请求/login, 可以此处修改,指定自己的登录action
                .loginPage("/login.html")    // 登录的页面
                //.defaultSuccessUrl("/")      // 成功登录的跳转请求
                /* 登录成功Handler */
                .successHandler(authenticationSuccessHandler())
                /* 登录失败的处理器 */
                .failureHandler(authenticationFailureHandler())
//        http.exceptionHandling()
//                /* 配置登录超时或者未登录处理器 */
//                .authenticationEntryPoint(customizeAuthenticationEntryPoint)
//                /* 配置权限不足处理器 */
//                .accessDeniedHandler(customizeAccessDeniedHandler);

            /* 登出处理器 并删除 SESSION  */
            .and().logout()
                .addLogoutHandler(logoutHandler())
                //.logoutSuccessHandler(customizeLogoutSuccessHandler)
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true)//用户的HTTP session将会在退出时被失效。在一些场景下，这是必要的（如用户拥有一个购物车时）

            .and().rememberMe().rememberMeParameter("rememberme")
                //.alwaysRemember(true)//总是记住,不管前端页面选中与否都  实现 记住我 功能
                .userDetailsService(userDetailsService)
                .tokenRepository(persistentTokenRepository())//设置数据访问层
                .tokenValiditySeconds(5 * 60)//7 * 24 * 60 * 60

            .and().sessionManagement()//在 rememberMe() 之后
                .maximumSessions(1)
                .expiredSessionStrategy(sessionInformationExpiredStrategy());//SESSION失效(账号被挤下线)处理逻辑
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    // RememberMe PersistentTokenRepository
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        //tokenRepository.setCreateTableOnStartup(true);
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    //登入成功处理器
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return new AuthenticationSuccessHandler() {
            @Autowired
            private SessionRegistry sessionRegistry;
            @Autowired
            private JwtTokenUtil jwtTokenUtil;
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                PrintWriter out = response.getWriter();
                logger.info("{}.sessionCreated() --> 创建Session: {}, 在线人数：{}",this.getClass().getName(), request.getSession().getId(), sessionRegistry.getAllPrincipals().size());
                String token = jwtTokenUtil.generateToken(SecurityUtils.getUsername());
                out.write(JSON.toJSONString(HttpResult.success().add("token", token)));
                out.flush();
                out.close();
            };
        };
    }
    //登录失败处理器
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler(){
        return (request, response, auth) -> {
            response.setContentType("application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            out.write(JSON.toJSONString(HttpResult.error()));
            out.flush();
            out.close();
        };
    }
    // 登出操作
    @Bean
    public LogoutHandler logoutHandler() {
        return ((request, response, authentication) -> {
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            PrintWriter out = null;
            try {
                out = response.getWriter();
            } catch (IOException e) {
                e.printStackTrace();
            }
            out.write(JSON.toJSONString(HttpResult.success("登出成功")));
            out.flush();
            out.close();
        });
    }

    @Bean
    public SessionInformationExpiredStrategy sessionInformationExpiredStrategy() {
        return (sessionInformationExpiredEvent) -> {
            HttpServletResponse resp = sessionInformationExpiredEvent.getResponse();
            resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            resp.getWriter().write(JSON.toJSONString(new HttpResult(10001, "检测到用户在别处登录")));
        };
    }
}
